
package com.idealighter.game.games.zjh.manager;

import static com.idealighter.game.games.zjh.struct.ZjhCardsType.BAO_ZI;
import static com.idealighter.game.games.zjh.struct.ZjhCardsType.SHUN_JIN;

import com.google.common.eventbus.Subscribe;
import com.google.inject.Inject;
import com.google.inject.Singleton;

import com.idealighter.game.common.CommonMsgMgr;
import com.idealighter.game.core.assertions.HuohuaAssert;
import com.idealighter.game.core.common.Game;
import com.idealighter.game.core.error.ErrorCode;
import com.idealighter.game.dblog.service.DbLogService;
import com.idealighter.game.dictionary.dic.ZhajinhuaTimeDic;
import com.idealighter.game.dictionary.domain.ZhajinhuaRoomDomain;
import com.idealighter.game.dictionary.domwrapper.ZhajinhuaRoomDomWrapper;
import com.idealighter.game.event.manager.EventMgr;
import com.idealighter.game.event.struct.ClearGameDataEvent;
import com.idealighter.game.event.struct.GameClearExitEvent;
import com.idealighter.game.event.struct.PlayerExitEvent;
import com.idealighter.game.event.struct.ShutdownGameEvent;
import com.idealighter.game.event.struct.ShutdownRoomEvent;
import com.idealighter.game.event.struct.StartGameEvent;
import com.idealighter.game.event.struct.StartRoomEvent;
import com.idealighter.game.gamehall.event.EnterRoomEvent;
import com.idealighter.game.gamehall.event.EnterTableEvent;
import com.idealighter.game.gamehall.event.ExitRoomEvent;
import com.idealighter.game.gamehall.event.ExitTableEvent;
import com.idealighter.game.gamehall.event.RoomMemInfoUpdateEvent;
import com.idealighter.game.games.common.AbstractRoom;
import com.idealighter.game.games.common.InstanceState;
import com.idealighter.game.games.common.SeatState;
import com.idealighter.game.games.zjh.dto.BillInfo;
import com.idealighter.game.games.zjh.message.ResGameInfoMsg;
import com.idealighter.game.games.zjh.struct.ZjhCard;
import com.idealighter.game.games.zjh.struct.ZjhCards;
import com.idealighter.game.games.zjh.struct.ZjhCardsType;
import com.idealighter.game.games.zjh.struct.ZjhRoom;
import com.idealighter.game.games.zjh.struct.ZjhSeat;
import com.idealighter.game.games.zjh.struct.ZjhTable;
import com.idealighter.game.games.zjh.util.ZjhCardsTypeComparator;
import com.idealighter.game.log.core.LogReason;
import com.idealighter.game.log.struct.game.Game17WinLoseLog;
import com.idealighter.game.log.struct.game.GameAfeeLog;
import com.idealighter.game.login.manager.LoginMgr;
import com.idealighter.game.message.core.ResMessage;
import com.idealighter.game.player.manager.PlayerMgr;
import com.idealighter.game.player.service.bo.PlayerBo;
import com.idealighter.game.player.struct.Player;
import com.idealighter.game.schedule.manager.ScheduleMgr;
import com.idealighter.game.server.ExecutorMgr;
import com.idealighter.game.server.core.executor.DisruptorExecutor;
import com.idealighter.utils.check.EmptyUtil;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.TimeUnit;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 扎金花.
 */
@Singleton
public class ZjhMgr {
  private static final Logger LOG = LoggerFactory.getLogger(ZjhMgr.class);
  // 玩家手上的牌数
  public static final int PLAYER_CARDS = 3;
  // 炸金花定时服务器增加的延时，因为与网络延时等原因
  private static final int ADDED_DELAY = 5;

  private final EventMgr eventMgr;
  // 游戏线程executor,可以执行普通任务和定时任务
  private final DisruptorExecutor gameExecutor;

  @Inject
  private CommonMsgMgr commonMsgMgr;
  @Inject
  private PlayerMgr playerMgr;
  @Inject
  private ZjhControlScript controlScript;
  @Inject
  private ScheduleMgr scheduleMgr;
  @Inject
  private LoginMgr loginMgr;
  @Inject
  private ZjhMsgMgr msgMgr;
  @Inject
  private ZjhDataMgr dataMgr;
  @Inject
  private ZhajinhuaTimeDic timeDic;

  /**
   * 构造函数.
   * 
   * @param eventMgr .
   * @param executorMgr .
   */
  @Inject
  public ZjhMgr(EventMgr eventMgr, ExecutorMgr executorMgr) {
    this.eventMgr = eventMgr;
    eventMgr.register(this);
    this.gameExecutor = executorMgr.getGameExecutor(Game.ZJH.getModuleId());
  }

  /**
   * 订阅玩家退出游戏事件,在gameExecutor中执行,避免线程安全问题.
   * 
   * @param event .
   */
  @Subscribe
  public void onPlayerExitGame(PlayerExitEvent event) {
    Player player = event.player;
    if (player != null) {
      long playerId = player.getId();
      if (player.curRoom instanceof ZjhRoom) {
        gameExecutor.execute(() -> {
          ZjhSeat seat = dataMgr.getPlayerSeat(playerId);
          if (seat != null) {
            doExitTabl(seat);
          }

          // 玩家是否在炸金花房间中
          if (player.curRoom instanceof ZjhRoom) {
            // 退出房间
            exitRoom(player);

            // 触发退出事件
            GameClearExitEvent gameClearExitEvent =
                new GameClearExitEvent(player, Game.ZJH.getDesc());
            eventMgr.post(gameClearExitEvent);
          }
        });
      }
    }
  }

  /**
   * 检验是否房间关闭，如果桌子不在游戏中或者游戏正在休息，顺便把桌子关闭 .
   *
   * @author abin
   * @date 2018年4月28日 下午3:41:20
   * @param table 桌子.
   */
  private boolean checkClose(ZjhTable table) {
    boolean remove = false;
    if (table.getRoom().getInstanceState() == InstanceState.TO_REMOVE) {
      boolean noExit = false;
      if (table.isReadied()) {
        noExit = true;
      }
      if (!noExit) {
        for (Long playerId : table.players()) {
          Player player = playerMgr.getPlayer(playerId);
          if (player != null) {
            kickoutForRoomClose(player);
          }
        }
        ZjhRoom room = table.getRoom();
        room.removeTable(table.getId().intValue());

        table = null;
        remove = true;
      }
    }
    return remove;

  }

  private void deleteRooms(List<ZjhRoom> deleteRooms) {
    if (EmptyUtil.listIsNotEmpty(deleteRooms)) {
      for (Iterator<ZjhRoom> iterator = deleteRooms.iterator(); iterator.hasNext();) {
        ZjhRoom room = iterator.next();
        List<ZjhTable> tables = new ArrayList<>(room.tables());
        for (Iterator<ZjhTable> tableIterator = tables.iterator(); tableIterator.hasNext();) {
          ZjhTable table = tableIterator.next();
          checkClose(table);
        }
        dataMgr.checkRemoveInstance(room.getId().intValue(), room.getInstanceId());
      }
    }
  }

  /**
   * .
   * 
   * @param event .
   */
  @Subscribe
  public void onStartRoom(StartRoomEvent event) {
    gameExecutor.execute(() -> {
      if (event.getGame() == Game.ZJH) {
        List<ZjhRoom> deleteRooms = dataMgr.startRoom(event.getRoomId(), true);
        deleteRooms(deleteRooms);
      }
    });
  }

  /**
   * .
   * 
   * @param event .
   */
  @Subscribe
  public void onShutDownRoom(ShutdownRoomEvent event) {
    gameExecutor.execute(() -> {
      if (event.getGame() == Game.ZJH) {
        List<ZjhRoom> deleteRooms = dataMgr.deleteRoom(event.getRoomId());
        deleteRooms(deleteRooms);
      }

    });
  }


  /**
   * 开启游戏 .
   *
   * @author abin
   * @date 2018年5月18日 上午11:24:51
   * @param event 开启游戏事件.
   */
  @Subscribe
  public void onStartGame(StartGameEvent event) {
    gameExecutor.execute(() -> {
      if (event.getGame() == Game.ZJH) {
        List<Integer> ids = dataMgr.reloadRoom();
        if (EmptyUtil.listIsNotEmpty(ids)) {
          for (Integer id : ids) {
            List<ZjhRoom> deleteRooms = dataMgr.startRoom(id, false);
            deleteRooms(deleteRooms);
          }
        }
      }
    });
  }


  /**
   * 关闭游戏事件.
   * 
   * @param event 事件.
   */
  @Subscribe
  public void onShutdownGame(ShutdownGameEvent event) {
    gameExecutor.execute(() -> {
      if (event.getGame() == Game.ZJH) {
        Collection<ZjhRoom> rooms = dataMgr.allRooms();
        if (!EmptyUtil.isEmpty(rooms)) {
          for (ZjhRoom room : rooms) {
            List<ZjhRoom> deleteRooms = dataMgr.deleteRoom(room.getId().intValue());
            deleteRooms(deleteRooms);
          }
        }
      }
    });
  }

  @Subscribe
  public void onClearGameData(ClearGameDataEvent event) {
    gameExecutor.execute(() -> doClearGameData(event.playerId));
  }

  /**
   * 进入游戏大厅.
   * 
   * @param player .
   */
  public ResMessage enterGameHall(Player player) {
    if (player.curRoom != null) {
      Game game = player.curRoom.game();
      LOG.warn("[炸金花]玩家[{}][{}]在[{}]游戏中，暂时无法进入游戏", player.getId(), player.getPlayerName(),
          game.getDesc());
      HuohuaAssert.isTrue(false, ErrorCode.GAME_ALREADY_IN, game.getDesc());
    }

    LOG.info("[炸金花]玩家[{}][{}]进入大厅成功", player.getPlayerName(), player.getId());
    return msgMgr.sendEnterGameHallMsg();
  }

  /**
   * 玩家进入房间.
   * 
   * @param player .
   * @param roomId .
   */
  public ResMessage enterRoom(Player player, int roomId) {
    AbstractRoom curRoom = player.curRoom;
    if (curRoom != null) {
      Game game = curRoom.game();
      LOG.warn("[炸金花]玩家[{}][{}]当前在游戏[{}]房间[{}]中不能进入房间", player.getId(), player.getPlayerName(),
          curRoom.game().getDesc(), curRoom.getId());
      HuohuaAssert.isTrue(false, ErrorCode.GAME_ALREADY_IN, game.getDesc());
    }

    ZjhRoom room = dataMgr.getNewestRoom(roomId);
    if (room == null) {
      LOG.error("[炸金花]玩家[{}][{}]进入的房间[{}]不存在", player.getPlayerName(), player.getId(), roomId);
      HuohuaAssert.isTrue(false);
    }

    ZhajinhuaRoomDomain roomDom = room.getRoomDomain();
    if (roomDom.getIsActive() != 1) {
      LOG.error("[炸金花]玩家[{}][{}]进入的房间[{}]已经关闭", player.getPlayerName(), player.getId(), roomId);
      HuohuaAssert.isTrue(false);
    }

    if (player.getGold() < roomDom.getLower()) {
      LOG.warn("[炸金花]玩家[{}][{}]进入的房间[{}]低于下限[{}]金币", player.getPlayerName(), player.getId(), roomId,
          roomDom.getLower());
      HuohuaAssert.isTrue(false, ErrorCode.PLAYER_GOLD_NOT_ENOUGH);
    }
    int upper = roomDom.getUpper();
    if (upper > 0 && player.getGold() > upper) {
      LOG.warn("[炸金花]玩家[{}][{}]进入的房间[{}]高于上限[{}]金币", player.getPlayerName(), player.getId(), roomId,
          roomDom.getUpper());
      HuohuaAssert.isTrue(false, ErrorCode.PLAYER_GOLD_BEYOND_MAX_GOLD);
    }

    if (!player.vip()
        && room.getPlayers().size() >= roomDom.getMaxNum() * roomDom.getOrdinarPeople() / 100) {
      LOG.warn("[炸金花]玩家[{}][{}]进入的房间[{}]普通玩家人数已超过上限", player.getPlayerName(), player.getId(),
          roomId, roomDom.getMaxNum());
      HuohuaAssert.isTrue(false, ErrorCode.GAME_NOT_VIP_ROOM_FULL);
    }

    if (room.getPlayers().size() >= roomDom.getMaxNum()) {
      LOG.warn("[炸金花]玩家[{}][{}]进入的房间[{}]已超过最大人数[{}]", player.getPlayerName(), player.getId(),
          roomId, roomDom.getMaxNum());
      HuohuaAssert.isTrue(false, ErrorCode.GAME_ROOM_FULL);
    }

    // 修改玩家当前房间
    player.curRoom = room;
    room.getPlayers().add(player.getId());

    // 触发进入房间事件
    eventMgr.post(new EnterRoomEvent(player, room));

    // 发送进入房间消息
    LOG.info("[炸金花]玩家[{}][{}]进入房间[{}]成功", player.getPlayerName(), player.getId(), roomId);
    return msgMgr.sendEnterRoomMsg(room);
  }

  public ResMessage resExitRoom(Player player) {
    exitRoom(player);
    return ResMessage.DEFAULT;
  }

  private void kickoutForRoomClose(Player player) {
    ZjhSeat seat = dataMgr.getPlayerSeat(player.getId());
    if (seat != null) {
      doExitTabl(seat);
    }
    if (player.curRoom != null) {
      doExitRoom(player);
      commonMsgMgr.noticeRoomCloseKickout(player);
    }
  }

  private void doExitRoom(Player player) {
    AbstractRoom curRoom = player.curRoom;
    ZjhSeat seat = dataMgr.getPlayerSeat(player.getId());

    HuohuaAssert.isTrue(curRoom instanceof ZjhRoom);
    HuohuaAssert.isTrue(seat == null, ErrorCode.GAME_EXIT_ROOM_EXIT_SIT_FIRST);

    player.curRoom = null;
    ZjhRoom room = (ZjhRoom) curRoom;
    room.getPlayers().remove(player.getId());

    LOG.info("[炸金花]玩家[{}][{}]退出房间[{}]", player.getPlayerName(), player.getId(), room.getId());
    // 触发退出房间事件
    eventMgr.post(new ExitRoomEvent(player, curRoom));
  }

  /**
   * 玩家退出房间.
   * 
   * @param player .
   */
  public void exitRoom(Player player) {
    doExitRoom(player);

    msgMgr.sendExitRoomMsg(player);
  }

  /**
   * 筹码换算金币,只会是多个金币等于一个筹码.
   * 
   * @param roomDomain .
   * @param chips .
   */
  public long convertGold(ZhajinhuaRoomDomain roomDomain, long chips) {
    return chips * roomDomain.getProportionGold() / roomDomain.getProportionChips();
  }

  /**
   * 金币换算筹码.
   * 
   * @param gold .
   * @return .
   */
  public long convertChip(ZhajinhuaRoomDomain roomDomain, long gold) {
    return gold * roomDomain.getProportionChips() / roomDomain.getProportionGold();
  }

  /**
   * 快速进入.
   * 
   * @param player .
   * @param roomId .
   */
  public ResMessage fastEnterTable(Player player, int roomId) {
    if (!(player.curRoom instanceof ZjhRoom)) {
      LOG.error("[炸金花]玩家[{}][{}]当前不在房间中不能直接进入桌子", player.getPlayerName(), player.getId());
      HuohuaAssert.isTrue(false);
    }

    ZjhSeat seat = dataMgr.getPlayerSeat(player.getId());
    if (seat != null) {
      LOG.error("[炸金花]玩家[{}][{}]已经在牌桌中不能重复进入牌桌", player.getPlayerName(), player.getId());
      HuohuaAssert.isTrue(false);
    }

    ZjhRoom room = (ZjhRoom) player.curRoom;
    if (room == null) {
      LOG.error("[炸金花]玩家[{}][{}]快速进入的房间[{}]不存在", player.getPlayerName(), player.getId(), roomId);
      HuohuaAssert.isTrue(false);
    }

    ZjhSeat emptySeat = findEmptySeat(player, room);
    HuohuaAssert.isTrue(emptySeat != null, ErrorCode.GAME_NOT_SIT);

    doEnterTable(player, emptySeat, null);
    return ResMessage.DEFAULT;
  }

  /**
   * 玩家进入座位.
   * 
   * @param player .
   * @param seat .
   */
  private void doEnterTable(Player player, ZjhSeat seat, String tablePwd) {
    int roomId = seat.getTable().getRoom().getId().intValue();
    ZjhTable table = seat.getTable();

    if (table.isReadied()) {
      LOG.warn("[炸金花]玩家[{}][{}]进入的桌子[{}]已经开始游戏", player.getPlayerName(), player.getId(),
          table.getId());
      HuohuaAssert.isTrue(false);
    }

    ZhajinhuaRoomDomain roomDom = table.getRoom().getRoomDomain();
    if (player.getGold() < roomDom.getLower()) {
      LOG.warn("[炸金花]玩家[{}][{}]进入的房间[{}]低于下限[{}]金币", player.getPlayerName(), player.getId(), roomId,
          roomDom.getLower());
      HuohuaAssert.isTrue(false, ErrorCode.PLAYER_GOLD_NOT_ENOUGH);
    }

    long upper = roomDom.getUpper();
    if (upper > 0L && player.getGold() > upper) {
      LOG.warn("[炸金花]玩家[{}][{}]进入的房间[{}]高于上限[{}]金币", player.getPlayerName(), player.getId(), roomId,
          roomDom.getUpper());
      HuohuaAssert.isTrue(false, ErrorCode.PLAYER_GOLD_BEYOND_MAX_GOLD);
    }

    if (player.getGold() < roomDom.getAfee()) {
      LOG.warn("[炸金花]玩家[{}][{}]进入桌子金币不足台费", player.getPlayerName(), player.getId());
      HuohuaAssert.isTrue(false, ErrorCode.GAME_NOT_ENOUGH_FEE);
    } else {
      playerMgr.minusGold(player, roomDom.getAfee(), LogReason.ZJH_ENTER_TABLE_MINUS_AFEE);
      DbLogService.log(new GameAfeeLog(player, table, roomDom.getAfee()));
    }

    seat.setPlayerId(player.getId());
    seat.setPlayerName(player.getPlayerName());
    seat.setSex(player.getSex());
    seat.setRobot(player.robot());
    seat.setState(SeatState.SEATED);
    seat.setTotalChips(convertChip(roomDom, player.getGold()));

    // 更新玩家座位
    dataMgr.updatePlayerSeats(player.getId(), seat);
    // 触发进入桌子事件
    eventMgr.post(new EnterTableEvent(player, seat));

    player.curSeat = seat;

    // 向玩家发送进入牌桌结果信息
    msgMgr.sendEnterTableMsg(player, table, seat.getTotalChips());
    msgMgr.sendOtherEnterTableMsg(seat);
    LOG.info("[炸金花]玩家[{}][{}]进入桌子[{}]座位[{}]成功", player.getPlayerName(), player.getId(),
        table.getId(), seat.getOrder());

    int readyTime = timeDic.map().get(1).getTime();
    // 进入座位后如果没有在指定时间内准备好，则退出座子
    seat.stepFuture = scheduleMgr.schedule(() -> {
      doExitTabl(seat);
      exitRoom(player);
    }, readyTime + ADDED_DELAY, TimeUnit.SECONDS, gameExecutor);
  }

  /**
   * 获取游戏最新信息 .
   *
   * @param player 玩家.
   * @return 信息.
   */
  public ResMessage gameLastInfo(Player player) {
    AbstractRoom curRoom = player.curRoom;
    ZjhSeat seat = dataMgr.getPlayerSeat(player.getId());

    ResMessage result = null;
    if (curRoom != null && seat != null) {
      ZjhRoom zjhRoom = (ZjhRoom) curRoom;
      seat.setTotalChips(convertChip(zjhRoom.getRoomDomain(), player.getGold()));
      result = msgMgr.sendGameLastInfo(seat);
    } else {
      result = new ResGameInfoMsg();
    }
    return result;
  }

  /**
   * 玩家退出牌桌.
   * 
   * @param player .
   */
  public ResMessage exitTable(Player player) {
    ZjhSeat seat = dataMgr.getPlayerSeat(player.getId());
    if (seat == null) {
      LOG.error("玩家[{}][{}]没在牌桌中不能退出牌桌", player.getPlayerName(), player.getId());
      HuohuaAssert.isTrue(false);
    }

    // 退出桌子
    doExitTabl(seat);
    return ResMessage.DEFAULT;
  }

  /**
   * 退出桌子.
   * <p/>
   * 1.玩家正常退出牌桌,能在在线玩家中拿到player,正常退出桌子
   * <p/>
   * 2.玩家非正常退出(如掉线)后,从活跃玩家或数据库中拿到PlayerDomain，结算后马上更新缓冲和数据库
   * 
   * @param seat .
   */
  private void doExitTabl(ZjhSeat seat) {
    long playerId = seat.getPlayerId();
    final Player player = playerMgr.getPlayer(playerId);
    ZjhTable table = seat.getTable();

    /*
     * 退桌处理 1.牌桌准备前，任何人随意退 2.牌桌准备后，任何人退，没有弃牌则弃牌，再退
     */
    if (table.isReadied()) { // 都准备好
      doDiscard(seat);
    }

    // 发送退出房间消息
    msgMgr.sendExitTableMsg(seat);
    dataMgr.removePlayerSeat(seat.getPlayerId());
    LOG.info("[炸金花]玩家[{}][{}]退出桌子[{}]", seat.getPlayerId(), seat.getPlayerName(),
        seat.getTable().getId());
    seat.clear();
    player.curSeat = null;
    player.curTable = null;

    // 3个人在桌子中，2个人准备，剩下那个人没有准备则准备结束
    if (!table.isReadied() && readiedOver(table)) { // 都准备完成,对所有玩家进行游戏过程定时(没有对每个步骤定时)
      readyOver(table);
    }

    // 触发退出桌子事件
    eventMgr.post(new ExitTableEvent(player, seat));
  }

  /**
   * 玩家准备.
   * 
   * @param player .
   */
  public ResMessage ready(Player player) {
    // 座位
    ZjhSeat seat = dataMgr.getPlayerSeat(player.getId());

    if (seat == null) {
      LOG.error("[炸金花]玩家[{}][{}]还未进入牌桌不能准备", player.getPlayerName(), player.getId());
      HuohuaAssert.isTrue(false);
    }

    if (seat.readied()) {
      LOG.warn("[炸金花]玩家[{}][{}]已经准备好,请勿重复准备", player.getPlayerName(), player.getId());
      return ResMessage.DEFAULT;
    }

    ZjhTable table = seat.getTable();
    ZhajinhuaRoomDomain roomDomain = table.getRoom().getRoomDomain();

    // 校验玩家身上的钱换成的筹码是否足够(封顶)
    if (seat.getTotalChips() < roomDomain.getTop()) {
      LOG.warn("[炸金花]玩家[{}][{}]玩家身上的筹码不足[{}(封顶)]不能准备", player.getPlayerName(), player.getId(),
          roomDomain.getTop());
      HuohuaAssert.isTrue(false, ErrorCode.GAME_CHIP_NOT_ENOUGH);
    }

    seat.setState(SeatState.READY);
    // 清除准备计时
    seat.stepFuture.cancel(false);

    msgMgr.sendReadyMsg(seat);
    LOG.info("[炸金花]玩家[{}][{}]准备好", player.getPlayerName(), player.getId());

    // 向同桌的玩家发送该座位的玩家已经准备好
    eventMgr.post(new RoomMemInfoUpdateEvent(player));

    // 计算桌子是否准备好
    if (readiedOver(table)) { // 都准备完成,对所有玩家进行游戏过程定时(没有对每个步骤定时)
      readyOver(table);
    }

    return ResMessage.DEFAULT;
  }

  /**
   * 桌子是否准备好,会改变桌子readied值.
   * 
   * @param table .
   * @return . 返回桌子是否准备好
   */
  private boolean readiedOver(ZjhTable table) {
    boolean tableReadied = true;
    // 准备好的玩家
    int readiedNum = 0;
    for (ZjhSeat s : table.getSeats()) {
      if (s.getPlayerId() > 0) {
        if (!s.readied()) {
          tableReadied = false;
          break;
        }
        readiedNum++;
      }
    }
    if (readiedNum <= 1) { // 必须大于1人才算准备好
      tableReadied = false;
    }

    table.setReadied(tableReadied);
    return tableReadied;
  }

  /**
   * 桌子全部准备完成.
   * 
   * @param table .
   */
  private void readyOver(ZjhTable table) {
    ZhajinhuaRoomDomWrapper roomDom = table.getRoom().getRoomDomain();

    Collections.shuffle(table.getCards());
    // 控制发牌
    controlScript.controlDealCards(table);
    // int cardIndex = 0;
    for (ZjhSeat s : table.getSeats()) {
      if (s.getPlayerId() > 0) {
        s.setState(SeatState.GAMING);
        // s.cards = new ZjhCards(table.cards.subList(cardIndex,
        // cardIndex += PLAYER_CARDS));
        s.setTotalChips(s.getTotalChips() - roomDom.getBase());
        s.setBetedchips(s.getBetedchips() + roomDom.getBase());
        table.setTotalBetChips(table.getTotalBetChips() + roomDom.getBase());
        msgMgr.sendChipsChangeMsg(s);
        playerMgr.addGold(s.getPlayerId(), -convertGold(roomDom, roomDom.getBase()),
            LogReason.ZJH_READY_MINUS_BASE_BET);
        // 清除准备计时
        s.stepFuture.cancel(false);
        eventMgr.post(new RoomMemInfoUpdateEvent(playerMgr.getPlayer(s.getPlayerId())));

        BillInfo billInfo = new BillInfo();
        billInfo.setOrder(s.getOrder());
        billInfo.setPlayerName(s.getPlayerName());
        billInfo.setChips(-s.getBetedchips());
        billInfo.setCardsType(s.getCards().getType().getPower());
        for (ZjhCard card : s.getCards().getCards()) {
          billInfo.getCards().add(card.ordinal());
        }

        table.getBillInfos().put(s.getPlayerId(), billInfo);
      }
    }

    table.setNextOptSeat(table.randomSeat());
    scheduleDiscard(table.getNextOptSeat());
    msgMgr.sendReadyOverMsg(table);
  }

  /**
   * 玩家换桌.
   * 
   * @param player .
   */
  public ResMessage exchangeTable(Player player) {
    ZjhSeat seat = dataMgr.getPlayerSeat(player.getId());
    if (seat == null) {
      LOG.error("玩家[{}][{}]没在座位中不能换位置", player.getPlayerName(), player.getId());
      HuohuaAssert.isTrue(false);
    }

    if (seat.readied()) {
      LOG.error("[炸金花]玩家[{}][{}]已经准备好不能换桌子", player.getPlayerName(), player.getId());
      HuohuaAssert.isTrue(false, ErrorCode.ZJH_COULD_NOT_EXCHANGE_TABLE_GAME_READY);
    }

    ZjhRoom room = seat.getTable().getRoom();

    HuohuaAssert.isTrue(player.getGold() >= room.getRoomDomain().getLower(),
        ErrorCode.PLAYER_GOLD_NOT_ENOUGH);

    HuohuaAssert.isTrue(player.getGold() <= room.getRoomDomain().getUpper(),
        ErrorCode.PLAYER_GOLD_BEYOND_MAX_GOLD);

    // 退出桌子
    exitTable(player);
    // 交换到其他桌的位置
    ZjhSeat exchangedSeat = findEmptySeat(player, room);
    if (exchangedSeat == null) {
      LOG.warn("[炸金花]当前房间没有空位置给玩家[{}][{}]交换", player.getPlayerName(), player.getId());
      HuohuaAssert.isTrue(false, ErrorCode.ZJH_COULD_NOT_EXCHANGE_HAVE_NO_OTHER_TABLE);
    } else {
      // 进入位置
      doEnterTable(player, exchangedSeat, null);
    }
    return ResMessage.DEFAULT;
  }

  /**
   * 查找空位置,优先查找有人的桌子.
   * 
   * @param player .
   * @param room .
   * @return .
   */
  private ZjhSeat findEmptySeat(Player player, ZjhRoom room) {
    ZjhSeat emptySeat = null;
    ArrayList<ZjhTable> tables = new ArrayList<>(room.getTables().values());
    Collections.shuffle(tables);

    for (ZjhTable table : tables) {
      if (table.getPassword() != null) {
        continue;
      }
      if (table.isReadied()) {
        continue;
      }

      if (table.getLimitIp() != null && table.getLimitIp().equals(player.ip)) {
        continue;
      }

      if (player.getGold() < table.getLimitGold()) {
        continue;
      }

      boolean hasPlayer = false;
      ZjhSeat tableEmptySeat = null;

      List<ZjhSeat> seats = new ArrayList<>(table.getSeats());
      Collections.shuffle(seats);
      for (ZjhSeat seat : seats) {
        if (seat.getPlayerId() == 0) {
          emptySeat = seat;
          tableEmptySeat = seat;
        } else {
          hasPlayer = true;
        }
      }

      if (hasPlayer && tableEmptySeat != null) {
        return tableEmptySeat;
      }
    }

    return emptySeat;
  }

  /**
   * 下注.
   * 
   * @param player .
   * @param chips .
   */
  public ResMessage bet(Player player, long chips) {
    ZjhSeat seat = dataMgr.getPlayerSeat(player.getId());
    if (seat == null) {
      LOG.warn("[炸金花]玩家[{}][{}]不在牌桌中不能下注", player.getPlayerName(), player.getId());
      HuohuaAssert.isTrue(false, ErrorCode.INTERNAL_SERVER_ERROR);
    }

    if (seat.getState() != SeatState.GAMING) {
      LOG.warn("[炸金花]玩家[{}][{}]不在游戏状态不能下注", player.getPlayerName(), player.getId());
      HuohuaAssert.isTrue(false, ErrorCode.INTERNAL_SERVER_ERROR);
    }

    if (seat.getCards().getCards().size() == 0) {
      LOG.warn("[炸金花]玩家[{}][{}]还未发牌不能下注", player.getPlayerName(), player.getId());
      HuohuaAssert.isTrue(false, ErrorCode.INTERNAL_SERVER_ERROR);
    }

    if (seat.getTotalChips() < chips) {
      LOG.warn("[炸金花]玩家[{}][{}]筹码不足不能下注", player.getPlayerName(), player.getId());
      HuohuaAssert.isTrue(false, ErrorCode.GAME_CHIP_NOT_ENOUGH);
    }

    ZjhTable table = seat.getTable();
    if (table.getNextOptSeat() != seat) {
      LOG.warn("[炸金花]不该玩家[{}][{}]操作，不能下注", player.getPlayerName(), player.getId());
      HuohuaAssert.isTrue(false, ErrorCode.GAME_NOT_YOUR_TURN);
    }

    /*
     * 校验下注是否合法
     */
    ZhajinhuaRoomDomWrapper roomDomWrapper = table.getRoom().getRoomDomain();

    if (table.getPreBet() == 0) { // 玩家第一次下注
      if (!seat.isSawCard()) { // 未看牌
        if (!roomDomWrapper.getDarkChips().contains(chips)) {
          LOG.warn("[炸金花]玩家[{}][{}]第一次下注[{}]不在下注范围内", player.getPlayerName(), player.getId(),
              chips);
          HuohuaAssert.isTrue(false, ErrorCode.ZJH_BET_CHIP_ILLEGAL);
        }
      } else { // 看牌
        if (!roomDomWrapper.getBrightChips().contains(chips)) {
          LOG.warn("[炸金花]玩家[{}][{}]第一次下注[{}]不在下注范围内", player.getPlayerName(), player.getId(),
              chips);
          HuohuaAssert.isTrue(false, ErrorCode.ZJH_BET_CHIP_ILLEGAL);
        }
      }
    } else if (table.isPreDark()) { // 上一家暗注
      if (!seat.isSawCard()) { // 未看牌
        if (!(chips >= table.getPreBet() && roomDomWrapper.getDarkChips().contains(chips))) {
          LOG.warn("[炸金花]上家暗注[{}]玩家[{}][{}]下注[{}]不合法", table.getPreBet(), player.getPlayerName(),
              player.getId(), chips);
          HuohuaAssert.isTrue(false, ErrorCode.ZJH_BET_CHIP_ILLEGAL);
        }
      } else {
        if (!(chips >= table.getPreBet() * 2 && roomDomWrapper.getBrightChips().contains(chips))) {
          LOG.warn("[炸金花]上家暗注[{}]玩家[{}][{}]下注[{}]不合法", table.getPreBet(), player.getPlayerName(),
              player.getId(), chips);
          HuohuaAssert.isTrue(false, ErrorCode.ZJH_BET_CHIP_ILLEGAL);
        }
      }
    } else { // 上一家明注
      if (!seat.isSawCard()) { // 未看牌
        if (!(chips >= table.getPreBet() / 2 && roomDomWrapper.getDarkChips().contains(chips))) {
          LOG.warn("[炸金花]上家明注[{}]玩家[{}][{}]下注[{}]不合法", table.getPreBet(), player.getPlayerName(),
              player.getId(), chips);
          HuohuaAssert.isTrue(false, ErrorCode.ZJH_BET_CHIP_ILLEGAL);
        }
      } else {
        if (!(chips >= table.getPreBet() && roomDomWrapper.getBrightChips().contains(chips))) {
          LOG.warn("[炸金花]上家明注[{}]玩家[{}][{}]下注[{}]不合法", table.getPreBet(), player.getPlayerName(),
              player.getId(), chips);
          HuohuaAssert.isTrue(false, ErrorCode.ZJH_BET_CHIP_ILLEGAL);
        }
      }
    }

    // 超过封顶只能比牌
    if (table.getTotalBetChips() >= roomDomWrapper.getTop()) {
      LOG.warn("[炸金花]超过封顶玩家[{}][{}]不能下注只能比牌", player.getPlayerName(), player.getId());

      HuohuaAssert.isTrue(false, ErrorCode.ZJH_CHIP_REACH_TOP);
    }

    if (seat.getBetedchips() == roomDomWrapper.getBase()) { // 是否下过暗注？(通过第一把判断)
      seat.setDarked(!seat.isSawCard());
    }

    seat.stepFuture.cancel(false);
    seat.setTotalChips(seat.getTotalChips() - chips);
    seat.setBetedchips(seat.getBetedchips() + chips);
    table.setPreBet(chips); // 设置上一把下注金币
    table.setPreDark(!seat.isSawCard()); // 设置上一把是否暗注
    table.setTotalBetChips(table.getTotalBetChips() + chips); // 设置桌子总下注金币
    table.setNextOptSeat(table.nextOptSeat(seat)); // 轮到下一个操作的座位

    // 发送筹码变化消息
    msgMgr.sendChipsChangeMsg(seat);
    playerMgr.minusGold(player, convertGold(roomDomWrapper, chips), LogReason.ZJH_BET);
    msgMgr.sendBetMsg(seat, chips, table.getTotalBetChips());

    if (!table.gameOver() && table.getNextOptSeat() != null) {
      scheduleDiscard(table.getNextOptSeat());
    }

    return ResMessage.DEFAULT;
  }

  /**
   * 看牌.
   * 
   * @param player .
   */
  public ResMessage seeCard(Player player) {
    ZjhSeat seat = dataMgr.getPlayerSeat(player.getId());
    if (seat == null) {
      LOG.error("[炸金花]玩家[{}][{}]不在牌桌中不能看牌", player.getPlayerName(), player.getId());
      HuohuaAssert.isTrue(false);
    }

    if (seat.getCards().getCards().size() == 0) {
      LOG.error("[炸金花]玩家[{}][{}]还没发牌不能看牌", player.getPlayerName(), player.getId());
      HuohuaAssert.isTrue(false);
    }

    // 已经看过牌
    seat.setSawCard(true);
    if (seat.isRobot()) {
      msgMgr.sendRobotSeeCardMsg(seat);
    } else {
      msgMgr.sendSeeCardMsg(seat);
    }

    msgMgr.sendSawCardMsg(seat);
    return ResMessage.DEFAULT;
  }

  /**
   * 弃牌.
   * 
   * @param player .
   */
  public ResMessage discard(Player player) {
    ZjhSeat seat = dataMgr.getPlayerSeat(player.getId());
    if (seat == null) {
      LOG.warn("[炸金花]玩家[{}][{}]不在牌桌中不能看牌", player.getPlayerName(), player.getId());
      HuohuaAssert.isTrue(false);
    }

    doDiscard(seat);
    return ResMessage.DEFAULT;
  }

  /**
   * 弃牌.
   * 
   * @param seat .
   */
  private void doDiscard(ZjhSeat seat) {
    if (seat.getCards() == null) {
      LOG.warn("[炸金花]玩家[{}][{}]还未发牌不能再弃牌", seat.getPlayerName(), seat.getPlayerId());
      return;
    }

    ZjhTable table = seat.getTable();
    if (seat.isOver() || table.gameOver()) {
      LOG.warn("[炸金花]玩家[{}][{}]已经结束游戏不能再弃牌", seat.getPlayerName(), seat.getPlayerId());
      return;
    }

    int nextOptOrder = -1;
    long nextOptPlayerId = 0;
    seat.stepFuture.cancel(false);
    seat.setOver(true);

    if (table.gameOver()) {
      msgMgr.sendDiscardMsg(seat, nextOptOrder, nextOptPlayerId);
      doGameOver(table);
    } else {
      // 中途弃牌也要结算奖池
      if (seat.isControl()) {
        controlScript.billPrizePool(seat,
            convertGold(table.getRoom().getRoomDomain(), -seat.getBetedchips()));
      }

      ZjhSeat nextOptSeat = table.nextOptSeat(seat);

      // 该玩家操作时弃牌才通知客户端
      if (seat == table.getNextOptSeat()) {
        nextOptOrder = nextOptSeat.getOrder();
        nextOptPlayerId = nextOptSeat.getPlayerId();
        table.setNextOptSeat(nextOptSeat);

        scheduleDiscard(nextOptSeat);
      }

      msgMgr.sendDiscardMsg(seat, nextOptOrder, nextOptPlayerId);
    }

  }

  /**
   * 比牌.
   * 
   * @param player .
   * @param other .
   */
  public ResMessage versus(Player player, long other) {
    ZjhSeat seat = dataMgr.getPlayerSeat(player.getId());
    ZjhSeat otherSeat = dataMgr.getPlayerSeat(other);

    if (seat == null) {
      LOG.warn("[炸金花]玩家[{}][{}]不在牌桌中不能比牌", player.getPlayerName(), player.getId());
      HuohuaAssert.isTrue(false);
    }

    ZjhTable table = seat.getTable();
    if (other == -1) {
      otherSeat = table.randomVsSeat(seat);
    }

    if (otherSeat == null) {
      LOG.warn("[炸金花]玩家[{}][{}]比牌的玩家[{}]不在牌桌中不能比牌", player.getPlayerName(), player.getId(), other);
      HuohuaAssert.isTrue(false);
    }

    // 第一圈不能比牌
    for (ZjhSeat s : table.getSeats()) {
      if (s.getPlayerId() > 0 && !s.isOver() && s.getBetedchips() == 0) {
        LOG.warn("[炸金花]玩家[{}][{}]第一圈不能比牌", player.getPlayerName(), player.getId());
        HuohuaAssert.isTrue(false);
      }
    }

    seat.stepFuture.cancel(false);

    // 上家明注双倍,暗注4倍
    long versusChips =
        table.isPreDark() ? (seat.isSawCard() ? table.getPreBet() * 4 : table.getPreBet() * 2)
            : (seat.isSawCard() ? table.getPreBet() * 2 : table.getPreBet());
    if (seat.getTotalChips() < versusChips) {
      LOG.warn("[炸金花]玩家[{}][{}]筹码[{}]不够比牌筹码[{}]不能比牌", player.getPlayerName(), player.getId(),
          seat.getTotalChips(), versusChips);
      HuohuaAssert.isTrue(false);
    }

    boolean win = ZjhCardsTypeComparator.compare(seat.getCards(), otherSeat.getCards());
    if (win) {
      otherSeat.setOver(true);
    } else {
      seat.setOver(true);
    }

    seat.setTotalChips(seat.getTotalChips() - versusChips);
    seat.setBetedchips(seat.getBetedchips() + versusChips);
    table.setTotalBetChips(table.getTotalBetChips() + versusChips);
    playerMgr.minusGold(player, convertGold(table.getRoom().getRoomDomain(), versusChips),
        LogReason.ZJH_VERSUS);
    // 发送筹码变化消息
    msgMgr.sendChipsChangeMsg(seat);

    if (table.gameOver()) {
      msgMgr.sendVersusMsg(seat, otherSeat, win, null, table.getTotalBetChips());
      doGameOver(table);
    } else {
      table.setNextOptSeat(table.nextOptSeat(seat));
      if (!table.gameOver() && table.getNextOptSeat() != null) {
        msgMgr.sendVersusMsg(seat, otherSeat, win, table.getNextOptSeat(),
            table.getTotalBetChips());
        scheduleDiscard(table.getNextOptSeat());
      }
    }
    return ResMessage.DEFAULT;
  }

  /**
   * 弃牌.
   * 
   * @param seat .
   */
  private void scheduleDiscard(ZjhSeat seat) {
    seat.stepFuture = scheduleMgr.schedule(() -> doDiscard(seat),
        timeDic.get(2).getTime() + ADDED_DELAY, TimeUnit.SECONDS, gameExecutor);
  }

  /**
   * 该牌桌游戏结束.
   * 
   * @param table .
   */
  private void doGameOver(ZjhTable table) {
    for (ZjhSeat seat : table.getSeats()) {
      if (seat.getPlayerId() > 0) {
        boolean control = seat.isControl();
        doBill(seat);

        if (control) {
          BillInfo billInfo = table.getBillInfos().get(seat.getPlayerId());
          controlScript.billPrizePool(seat,
              convertGold(table.getRoom().getRoomDomain(), billInfo.getChips()));
        }
      }
    }

    msgMgr.sendGameOver(table);
    table.reset();

    if (checkClose(table)) {
      dataMgr.checkRemoveInstance(table.getRoom().getId().intValue(),
          table.getRoom().getInstanceId());
    }
  }

  /**
   * 座位结算.
   * 
   * @param seat .
   */
  private void doBill(ZjhSeat seat) {
    long playerId = seat.getPlayerId();
    ZjhTable table = seat.getTable();
    ZjhSeat winner = table.winner();
    ZjhCards cards = seat.getCards();
    ZjhCardsType cardsType = cards.getType();

    ZhajinhuaRoomDomWrapper roomDom = table.getRoom().getRoomDomain();
    // 封顶
    int top = roomDom.getTop();
    // 计算结算筹码(包含喜钱)
    long billChips = 0;
    // 赢得筹码,如果是赢家去掉已下注
    long winChips = 0;
    long betedchips = seat.getBetedchips();
    // 喜钱
    long luckChips = 0;

    if (seat == winner) { // 赢家结算
      billChips = table.getTotalBetChips();
      // 计算收的喜钱,喜钱数：豹子=封顶*2，顺金=封顶*1.5
      if ((cardsType == BAO_ZI || cardsType == SHUN_JIN) && seat.isDarked()) {
        luckChips += Math.rint(cardsType.getLuckMultiple() * top);
      }

      billChips += luckChips;
      seat.setTotalChips(seat.getTotalChips() + billChips);
      playerMgr.addGold(playerId, convertGold(roomDom, billChips), LogReason.ZJH_BILL);

      winChips = billChips - betedchips;
    } else { // 输家结算
      billChips = -seat.getBetedchips();
      winChips = billChips;
    }

    PlayerBo playerDom = playerMgr.selectPlayer(playerId);
    // 数据库输赢日志
    DbLogService.log(new Game17WinLoseLog(playerDom, seat, winChips, convertGold(roomDom, winChips),
        LogReason.ZJH_BILL));

    seat.resetButTotalChips();

    // 发送筹码变化消息
    msgMgr.sendChipsChangeMsg(seat);

    int beginTime = timeDic.get(1).getTime() + timeDic.get(3).getTime();
    // 游戏结束后没有在指定时间内准备好，则退出座子
    seat.stepFuture = scheduleMgr.schedule(() -> {
      doExitTabl(seat);
      exitRoom(playerMgr.getPlayer(playerId));
    }, beginTime + ADDED_DELAY, TimeUnit.SECONDS, gameExecutor);
    // 触发房间大厅成员信息变更事件
    eventMgr.post(new RoomMemInfoUpdateEvent(playerMgr.getPlayer(playerId)));

    BillInfo billInfo = table.getBillInfos().get(seat.getPlayerId());
    // 扣除自己的下注筹码
    billInfo.setChips(winChips);
    billInfo.setLuck(luckChips);
  }

  /**
   * 清除玩家,服务器出现bug，玩家卡在游戏中，后台可以清除玩家.
   * 
   * @param playerId .
   */
  private void doClearGameData(long playerId) {
    try {

      dataMgr.removePlayerSeat(playerId);
      /*
       * 清除玩家房间数据
       */
      for (ZjhRoom room : dataMgr.allRooms()) {
        room.getPlayers().remove(playerId);

        /*
         * 清除房间中玩家所在桌子数据
         */
        for (ZjhTable table : room.getTables().values()) {

          // 是否在桌子中
          boolean inTable = false;
          for (ZjhSeat s : table.getSeats()) {
            if (s.getPlayerId() > 0 && s.getPlayerId() == playerId) {
              inTable = true;
              break;
            }
          }

          if (inTable) { // 在桌子中清除数据
            for (ZjhSeat s : table.getSeats()) {
              if (s.getPlayerId() > 0) {
                dataMgr.removePlayerSeat(s.getPlayerId());
                room.getPlayers().remove(s.getPlayerId());

                /*
                 * 清除座位数据
                 */
                s.clear();
                Player player = playerMgr.getPlayer(s.getPlayerId());
                if (player != null) {
                  loginMgr.noticeLogoutForGameExit(player);
                }
              }
            }
          }
        }
      }

      Player player = playerMgr.getPlayer(playerId);
      if (player != null) {
        loginMgr.noticeLogoutForGameExit(player);
      }
    } catch (Exception e) {
      LOG.error("[炸金花]清除玩家[" + playerId + "]卡限失败", e);
    }
  }

}
